package com.zhoug.tmap.dianditu;

import android.text.TextUtils;

import androidx.annotation.NonNull;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.zhoug.common.utils.GsonUtils;
import com.zhoug.tmap.AreaBoundary;
import com.zhoug.tmap.Callback;
import com.zhoug.tmap.http.HttpClient;
import com.zhoug.tmap.http.Optional;
import com.zhoug.tmap.json.JsonObjectWrapper;
import com.zhoug.tmap.json.MyGsonUtils;
import com.zhoug.tmap.location.IGeocoder;
import com.zhoug.tmap.location.ZAddress;
import com.zhoug.tmap.location.ZLatLng;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import okhttp3.ResponseBody;

/**
 * 地名搜索
 *
 * @Author: zhoug
 * @Date: 2025-01-03
 * @Description:
 */
public class TianDiTuApi implements IGeocoder {
    private static final String BASE_URL = "http://api.tianditu.gov.cn/";
    /**
     * 搜索服务
     */
    private static final String SEARCH_URL = BASE_URL + "v2/search";
    /**
     * 地理编码 地址数据->坐标
     */
    private static final String GEOCODER_URL = BASE_URL + "geocoder";
    /**
     * 行政区划服务
     */
    private static final String SEARCH_AREA_URL = BASE_URL + "v2/administrative";

    private static final String KEY_POST_STR = "postStr";
    private static final String KEY_DS = "ds";
    private static final String KEY_TYPE = "type";
    private static final String KEY_TK = "tk";

    private static final String TYPE_QUERY = "query";
    private static final String TYPE_GEOCODE = "geocode";
    /**
     * 天地图key
     */
    public static String mapKey="";
    private HttpClient mHttpClient;
    private static Pattern PATTERN_DECIMAL = Pattern.compile("(\\d+[.]*\\d+)");
    private static Pattern PATTERN_LONLAT = Pattern.compile("((\\d+[.]*\\d+).+(\\d+[.]*\\d+))");

    private static final class InstanceHolder {
        public static final TianDiTuApi instance = new TianDiTuApi();
    }

    public static TianDiTuApi getInstance() {
        return InstanceHolder.instance;
    }

    protected TianDiTuApi() {
        mHttpClient = new HttpClient();
        mHttpClient.setBaseUrl(BASE_URL);
    }

    /**
     * 地址搜索服务
     *
     * @param searchParams
     * @param callback
     * @return
     */
    public Disposable search(@NonNull SearchParams searchParams, @NonNull Callback<SearchResult> callback) {
        //http://api.tianditu.gov.cn/v2/search
        // ?postStr={"keyWord":"商厦","queryType":12,"start":0,"count":10,"specify":"156110108"}
        // &type=query
        // &tk=413f916fb5a320afc19cae96d4092291
        Map<String, Object> map = new HashMap<>();
        map.put(KEY_POST_STR, GsonUtils.toJson(searchParams));
        map.put(KEY_TYPE, TYPE_QUERY);
        map.put(KEY_TK, mapKey);
        return get(SEARCH_URL, map, SearchResult.class, callback);
    }

    /**
     * 地理编码
     * 根据地址查询经纬度
     *
     * @param address
     * @param callback
     * @return
     */
    public Disposable geocoder(String address,@NonNull Callback<ZLatLng> callback) {
        // http://api.tianditu.gov.cn/geocoder
        // ?ds={"keyWord":"北京市延庆区延庆镇莲花池村前街50夕阳红养老院"}
        // &tk=413f916fb5a320afc19cae96d4092291
        SearchParams searchParams = new SearchParams();
        searchParams.setKeyWord(address);
        Map<String, Object> map = new HashMap<>();
        map.put(KEY_DS, GsonUtils.toJson(searchParams));
        map.put(KEY_TK, mapKey);
        return get(GEOCODER_URL, map, GeocoderResult.class, new Callback<GeocoderResult>() {
            @Override
            public void onSuccess(GeocoderResult data) {
                if (data != null && data.getLocation() != null) {
                    GeocoderLocation location = data.getLocation();
                    callback.onSuccess(new ZLatLng(location.getLat(), location.getLat()));
                } else {
                    callback.onSuccess(null);
                }
            }

            @Override
            public void onFailure(String error) {
                callback.onFailure(error);
            }
        });
    }

    /**
     * 逆地理编码
     * 根据经纬度查询地址
     *
     * @param longitude
     * @param latitude
     * @param callback
     * @return
     */
    @Override
    public Disposable geocoderReverse(double latitude, double longitude,  @NonNull Callback<ZAddress> callback) {
        //http://api.tianditu.gov.cn/geocoder
        // ?postStr={'lon':116.37304,'lat':39.92594,'ver':1}&type=geocode&tk=您的密钥
        Map<String, Double> lonlat = new HashMap<>();
        lonlat.put("lon", longitude);
        lonlat.put("lat", latitude);
        Map<String, Object> map = new HashMap<>();
        map.put(KEY_POST_STR, GsonUtils.toJson(lonlat));
        map.put(KEY_TK, mapKey);
        map.put(KEY_TYPE, TYPE_GEOCODE);
        return get(GEOCODER_URL, map, GeocoderResult.class, new Callback<GeocoderResult>() {
            @Override
            public void onSuccess(GeocoderResult data) {
                if (data != null && data.getResult() != null) {
                    GeocoderResult.Result result = data.getResult();
                    GeocoderResult.AddressComponent addressComponent = result.getAddressComponent();
                    ZAddress.Builder builder = null;
                    if (addressComponent != null) {
                        String provinceCode = formatAreaCode(addressComponent.getProvince_code());
                        String cotyCode = formatAreaCode(addressComponent.getCity_code());
                        String countyCode = formatAreaCode(addressComponent.getCounty_code());
                        String townCode = formatAreaCode(addressComponent.getTown_code());
                        builder = new ZAddress.Builder()
                                .latitude(latitude)
                                .longitude(longitude)
                                .country(addressComponent.getNation())//国家
                                .province(addressComponent.getProvince())
                                .provinceCode(provinceCode)
                                .city(addressComponent.getCity())
                                .cityCode(cotyCode)
                                .district(addressComponent.getCounty())
                                .districtCode(countyCode)
                                .town(addressComponent.getTown())
                                .townCode(townCode)
                                .street(addressComponent.getRoad());
                        if (!TextUtils.isEmpty(countyCode)) {
                            builder.adcode(countyCode);
                        } else if (!TextUtils.isEmpty(cotyCode)) {
                            builder.adcode(cotyCode);
                        } else if (!TextUtils.isEmpty(townCode)) {
                            builder.adcode(townCode);
                        } else if (!TextUtils.isEmpty(provinceCode)) {
                            builder.adcode(provinceCode);
                        }
                    }
                    String formattedAddress = result.getFormatted_address();
                    if (builder != null && !TextUtils.isEmpty(formattedAddress)) {
                        builder.address(formattedAddress);
                    }
                    callback.onSuccess(builder != null ? builder.build() : null);
                } else {
                    callback.onSuccess(null);
                }
            }

            @Override
            public void onFailure(String error) {
                callback.onFailure(error);
            }
        });
    }



    /**
     * 行政区划
     *
     * @param areaCode   区域代码  天地图要加156前缀 如:重庆500000,参数为156500000
     * @param childLevel 设置显示下级行政区级数（行政区级别包括：国家、省/直辖市、市、区/县多级数据 0、1、2、3,0：不返回下级行政区
     * @param extensions 是否需要轮廓数据 true、false
     * @param callback
     * @return
     */
    public Disposable searchArea(String areaCode, int childLevel, boolean extensions,  @NonNull Callback<AreaBoundary> callback) {
        //http://api.tianditu.gov.cn/v2/administrative
        // ?keyword=156110000&childLevel=0&extensions=true&tk=您的密钥
        Map<String, Object> map = new HashMap<>();
        map.put("keyword", areaCode);
        map.put("childLevel", childLevel);
        map.put("extensions", extensions);
        map.put(KEY_TK, mapKey);
        Observable<ResponseBody> observable = mHttpClient.getBaseRequests().get(SEARCH_AREA_URL, map);
        return observable.subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .map((Function<ResponseBody, Optional<AreaBoundary>>) responseBody -> {
                    AreaBoundary areaBoundary = null;
                    if (responseBody != null) {

                        JsonObject jsonObject = (JsonObject) MyGsonUtils.createJsonElement(responseBody.string());
                        JsonObjectWrapper wrapper = new JsonObjectWrapper();
                        wrapper.setJsonObject(jsonObject);
                        int status = wrapper.getAsInt("status", 0);
                        if (status == 200) {
                            JsonObjectWrapper data = new JsonObjectWrapper();
                            data.setJsonObject(wrapper.getAsJsonObject("data"));
                            JsonArray districts = data.getAsJsonArray("district");
                            if (districts != null && districts.size() > 0) {
                                JsonElement jsonElement = districts.get(0);
                                if (jsonElement instanceof JsonObject) {
                                    areaBoundary = new AreaBoundary();
                                    JsonObjectWrapper district = new JsonObjectWrapper();
                                    district.setJsonObject((JsonObject) jsonElement);
                                    areaBoundary.setAreaName(district.getAsString("name"));
                                    String gb = formatAreaCode(district.getAsString("gb"));

                                    areaBoundary.setAreaCode(gb);
                                    areaBoundary.setLevel(district.getAsInt("level", 0));
                                    JsonObject centerObj = district.getAsJsonObject("center");
                                    if (centerObj != null) {
                                        JsonObjectWrapper center = new JsonObjectWrapper();
                                        center.setJsonObject(centerObj);
                                        areaBoundary.setCenter(new ZLatLng(center.getAsDouble("lat", 0), center.getAsDouble("lng", 0)));
                                    }
                                    String boundary = district.getAsString("boundary");
                                    if (boundary != null) {
                                        boundary = boundary.replace("MULTIPOLYGON", "");
                                        boundary = boundary.substring(1, boundary.length() - 1);
                                        String[] split = boundary.split("\\)\\),");
                                        List<List<List<Double>>> boundarys = new ArrayList<>();
                                        areaBoundary.setBoundary(boundarys);
                                        for (String string : split) {
                                            List<List<Double>> b1 = new ArrayList<>();
                                            boundarys.add(b1);
                                            String s = string;
                                            s = s.replace("((", "");
                                            s = s.replace("))", "");
                                            String[] lonlats = s.split(",");
                                            for (String value : lonlats) {
                                                String lonlat = value;
                                                if (lonlat != null) {
                                                    lonlat = lonlat.trim();
                                                    //120.924398 38.390509
                                                    if (!lonlat.isEmpty()) {
                                                        String[] result = lonlat.split(" ");
                                                        if (result.length == 2) {
                                                            try {
                                                                List<Double> lonlatList = new ArrayList<>();
                                                                lonlatList.add(Double.parseDouble(result[0].trim()));
                                                                lonlatList.add(Double.parseDouble(result[1].trim()));
                                                                b1.add(lonlatList);
                                                            } catch (NumberFormatException e) {
                                                                throw new RuntimeException(e);
                                                            }
                                                        }
                                                    }
                                                }

                                            }

                                        }

                                    }

                                }


                            }

                        }
                    }
                    return Optional.create(areaBoundary);
                }).observeOn(AndroidSchedulers.mainThread())
                .subscribe(data -> callback.onSuccess(data.get()), throwable -> callback.onFailure(throwable.getMessage()));
    }

    /**
     * get请求
     *
     * @param url
     * @param params
     * @param clz
     * @param callback
     * @param <T>
     * @return
     */
    private <T> Disposable get(String url, Map<String, Object> params, Class<T> clz, Callback<T> callback) {
        Observable<ResponseBody> observable = mHttpClient.getBaseRequests().get(url, params);
        return observable.subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .map((Function<ResponseBody, Optional<T>>) responseBody -> {
                    if (responseBody != null) {
                        String string = responseBody.string();
                        T searchResult = GsonUtils.fromJson(string, clz);
                        return Optional.create(searchResult);
                    }
                    return Optional.create(null);
                }).observeOn(AndroidSchedulers.mainThread())
                .subscribe(data -> callback.onSuccess(data.get()), throwable -> callback.onFailure(throwable.getMessage()));

    }

    /**
     * get请求
     *
     * @param url
     * @param params
     * @param clz
     * @param callback
     * @param <T>
     * @return
     */
    private <T> Disposable getSync(String url, Map<String, Object> params, Class<T> clz, Callback<T> callback) {
        Observable<ResponseBody> observable = mHttpClient.getBaseRequests().get(url, params);
        return observable.map((Function<ResponseBody, Optional<T>>) responseBody -> {
                    if (responseBody != null) {
                        String string = responseBody.string();
                        T searchResult = GsonUtils.fromJson(string, clz);
                        return Optional.create(searchResult);
                    }
                    return Optional.create(null);
                })
                .subscribe(data -> callback.onSuccess(data.get()), throwable -> callback.onFailure(throwable.getMessage()));

    }

    /**
     * 去掉天地图区域代码的前缀
     *
     * @param areaCode
     * @return
     */
    public static String formatAreaCode(String areaCode) {
        if (areaCode != null && areaCode.startsWith("156")) {
            return areaCode.substring(3);
        }
        return areaCode;
    }

    /**
     * 区域代码添加天地图前缀
     *
     * @param areaCode
     * @return
     */
    public static String addAreaCodePrefix(@NonNull String areaCode) {
        return "156" + areaCode;
    }

    /**
     * 设置天地图的key
     * @param key
     */
    public static void setMapKey(String key){
        TianDiTuApi.mapKey=key;
    }
}
